import { useCallback, useReducer, useState } from 'react'
import { useMountedRef } from 'utils'
// 定义状态类型
interface State<T> {
  data: T | null
  stat: 'idle' | 'loading' | 'error' | 'success'
  error: Error | null
}

// 定义初始化状态
const defaultInitialState: State<null> = {
  data: null,
  stat: 'idle',
  error: null
}

const useSafeDispatch = <T>(dispatch: (...args: T[]) => void) => {
  const isMounted = useMountedRef()

  return useCallback((...args: T[]) => (isMounted.current ? dispatch(...args) : void 0), [dispatch, isMounted])
}

// TODO 使用reducer改造
export const useAsync = <T>(initialState?: State<T>) => {
  // 初始化状态, 用户传入的状态优先级更高
  const [state, dispatch] = useReducer((state: State<T>, action: Partial<State<T>>) => ({ ...state, ...action }), {
    ...defaultInitialState,
    ...initialState
  })
  const safeDispatch = useSafeDispatch(dispatch)

  // 惰性初始化,保存刷新数据的方法
  const [retry, setRetry] = useState(() => () => {})
  const setData = useCallback(
    (data: T) =>
      safeDispatch({
        data,
        error: null,
        stat: 'success'
      }),
    [safeDispatch]
  )

  const setError = useCallback(
    (error: Error) =>
      safeDispatch({
        data: null,
        error,
        stat: 'error'
      }),
    [safeDispatch]
  )
  // 触发异步请求,接收 Promise类型参数
  const run = useCallback(
    (promise: Promise<T>, runConfig?: { retry: () => Promise<T> }) => {
      // 如果为空 或者 不是promise 直接抛出异常
      if (!promise || !promise.then) {
        throw new Error('请传入Promise对象')
      }
      safeDispatch({ stat: 'loading' })
      // 保存 参数
      setRetry(() => () => {
        if (runConfig?.retry) {
          run(runConfig.retry(), runConfig)
        }
      })
      return promise
        .then(data => {
          // 判断组件是否加载成功
          setData(data)
          return data
        })
        .catch((error: Error) => {
          //catch会主动消化异常,不主动抛出, 外部是接收不到异常的
          setError(error)

          return Promise.reject(error)
        })
    },
    [setData, setError, safeDispatch]
  )

  return {
    isIdle: state.stat === 'idle',
    isLoading: state.stat === 'loading',
    isSuccess: state.stat === 'success',
    isError: state.stat === 'error',
    run,
    setData,
    setError,
    retry,
    ...state
  }
}
